using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;

using Standard;

using VSTalk.Engine.ViewModel.ContactList.ClientResources;
using VSTalk.Engine.ViewModel.ContactList.Filter;
using VSTalk.Engine.ViewModel.ContactList.Order;
using VSTalk.Model;
using VSTalk.Tools;

namespace VSTalk.Engine.ViewModel.ContactList
{
    public class ClientResourcesManager : IResourceUpdateHandler, IListener
    {
        private readonly IPresentationSource _presenter;
        private readonly IResourceFilter _resourceFilter;
        private readonly IResourceComparator _resourceComparator;
        private readonly IObservable<Unit> _trigger; 

        private readonly IList<IClientResource> _registeredResources = new List<IClientResource>();

        private readonly NullClientResource _nullResource = new NullClientResource();

        private IDisposable _triggerUnit;
        private ISubscription _roomUnit;
        private ISubscription _contactUnit;

        private ObservableCollection<IClientResource> ActiveResources
        {
            get { return _presenter.ActiveResources; }
        }

        public ClientResourcesManager(IPresentationSource presenter,
                IResourceFilter resourceFilter,
                IResourceComparator resourceComparator)
        {
            _presenter = presenter;
            _resourceFilter = resourceFilter;
            _resourceComparator = resourceComparator;
            
            _trigger = new object[]
            {
                resourceFilter, resourceComparator
            }.OfType<IObservableResource>().Aggregate(Observable.Return(Unit.Default), (agg, next) => agg.Merge(next.Trigger));
            
            ActiveResources.Add(_nullResource);
            PopulateResources();
            Update();
        }


        public void StartListen()
        {
            _roomUnit = _presenter.RoomSource.HandleAddRemoveChanges(
                                     added: room => RegisterResource(new RoomResourceViewModel(room)),
                                     removed: room => UnRegisterResource(_registeredResources.FirstOrDefault(resource => resource.Model == room)));
            _contactUnit = _presenter.InterlocutorSource.HandleAddRemoveChanges(
                                        added: interlocutor => RegisterResource(new InterlocutorResourceViewModel(interlocutor)),
                                        removed: interlocutor => UnRegisterResource(_registeredResources.FirstOrDefault(resource => resource.Model == interlocutor)));
            _triggerUnit = _trigger.Subscribe(unit => Update());
        }

        public void StopListen()
        {
            _triggerUnit.Dispose();
            _roomUnit.Unsubscribe();
            _contactUnit.Unsubscribe();
        }

        private void RegisterResource(IClientResource resource)
        {
            _registeredResources.Add(resource);
            resource.StartListen();
            resource.Subscribe(this);
            if (IsAccepted(resource))
            {
                AddResource(resource);
            }
        }

        private void UnRegisterResource(IClientResource resource)
        {
            if (resource == null)
            {
                return;
            }
            resource.StopListen();
            _registeredResources.Remove(resource);
            if (ActiveResources.Contains(resource))
            {
                RemoveResource(resource);
            }
        }

        private void PopulateResources()
        {
            foreach (var room in _presenter.RoomSource)
            {
                RegisterResource(new RoomResourceViewModel(room));
            }

            foreach (var contact in _presenter.InterlocutorSource)
            {
                RegisterResource(new InterlocutorResourceViewModel(contact));
            }
        }

        private void AddResource(IClientResource targetResource)
        {
            if (ActiveResources.Contains(_nullResource))
            {
                ActiveResources.Remove(_nullResource);
            }
            var position = Position(ActiveResources, targetResource);
            ActiveResources.Insert(position, targetResource);
        }

        private void RemoveResource(IClientResource resource)
        {
            ActiveResources.Remove(resource);
            if (ActiveResources.Count == 0)
            {
                ActiveResources.Add(_nullResource);
            }
        }

        private int Position(IEnumerable<IClientResource> resources, IClientResource target)
        {
            if (resources.Contains(target))
            {
                return OrderedResources(resources)
                        .TakeWhile(resource => resource != target).Count();
            }
            var concat = resources.Concat(new[]
            {
                    target
            });
            return OrderedResources(concat).TakeWhile(resource => resource != target).Count();
        }

        private IEnumerable<IClientResource> OrderedResources(IEnumerable<IClientResource> resources)
        {
            return resources.OrderBy(resource => resource, _resourceComparator.Comparator);
        }

        private bool IsAccepted(IClientResource clientResource)
        {
            return _resourceFilter.Apply(clientResource) == FilterResult.Accepted;
        }

        public void Update()
        {
            //remove
            var toRemove = ActiveResources.Where(resource => !IsAccepted(resource)).ToList();
            foreach (var resource in toRemove)
            {
                RemoveResource(resource);
            }

            //insert
            var toInsert = _registeredResources.Exclude(toRemove.Union(ActiveResources));
            foreach (var clientResource in toInsert.Where(IsAccepted))
            {
                AddResource(clientResource);
            }

            //reorder
            var clientResources = OrderedResources(ActiveResources).ToList();
            foreach (var target in clientResources)
            {
                var actualPosition = ActiveResources.IndexOf(target);
                var expectedPosition = clientResources.IndexOf(target);
                if (actualPosition != expectedPosition)
                {
                    ActiveResources.Move(actualPosition, expectedPosition);
                }
            }
        }
    }
}